#include "object/Scene.h"
#include "DefaultShader.h"
#include "WaterShader.h"
#include "structure/Crane.h"
#include "structure/Floor.h"
#include "structure/Water.h"
#include "cameras/CameraDefault.h"
#include "cameras/CameraAerial.h"
#include "cameras/CameraGround.h"
#include "cameras/CameraGroundBis.h"
#include "Constants.h"
#include "InputMgr.h"
#include "TextureMgr.h"
#include <glm/glm.hpp>
#include <glm/gtc/matrix_transform.hpp>

Scene::Scene() {
	programMgr = new GLProgramMgr();
	createScene();
	InputMgr::getInstance()->registerListener((KeyListener*)this);

	cameraSelected = 0;
	cameras.push_back(new CameraDefault());
	cameras.push_back(new CameraAerial());
	cameras.push_back(new CameraGround());
	cameras.push_back(new CameraGroundBis());
}

Scene::~Scene() {
	delete programMgr;

	std::list<GLStructure*>::iterator it = objects.begin();
	for ( ; it != objects.end(); it++) {
		delete *it;
	}
	objects.clear();

	std::vector<GLView*>::iterator it2 = cameras.begin();
	for ( ; it2 != cameras.end(); ++it2) {
		delete *it2;
	}
	cameras.clear();
}

void Scene::createScene() {
	DefaultShader defaultShader;
	GLuint prg = programMgr->registerShaders(defaultShader);

	Floor* floor = new Floor(prg, defaultShader.getConfig(), 20, 10, 1.5);
	addObject(floor);

	Crane* crane = new Crane(prg, defaultShader.getConfig(), CRANE_WIDTH, CRANE_HEIGHT);
	glm::mat4 transform = glm::mat4(1.0f);
	transform = glm::translate(transform, glm::vec3(0, 10/2 - CRANE_WIDTH/2 - 1, 0));
	crane->transform(transform);
	addObject(crane);

	WaterShader waterShader;
	prg = programMgr->registerShaders(waterShader);
	Water* water = new Water(prg, waterShader.getConfig(), 20, 20);
	transform = glm::mat4(1.0f);
	transform = glm::translate(transform, glm::vec3(-20/2, 10/2, -1));
	water->transform(transform);
	addObject(water);
}

void Scene::update() {
	glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

	// View (camera) Matrix
	glm::mat4 view_matrix = cameras.at(cameraSelected)->getViewMatrix();

	// Model Matrix
	glm::mat4 model_matrix = glm::mat4 ( 1.0f );

	// Projection Matrix
	glm::mat4 projection_matrix = glm::mat4 ( 1.0f );
	projection_matrix = glm::infinitePerspective( 60.0f , 4.0f / 3.0f, 0.1f);

	std::list<GLStructure*>::iterator it = objects.begin();
	for ( ; it != objects.end(); it++ ) {
		GLuint programHandler = (*it)->getProgramHandler();
		programMgr->runProgram(programHandler);

		/////////////////////////////////////////
		// Bind Dirtiness factor
		GLuint location_dirt_percentage = glGetUniformLocation( programHandler, "DirtPercentage");
		if( location_dirt_percentage >= 0 )
		{
			glUniform1f( location_dirt_percentage, dirtiness);
		}

		// Bind View MAtrix
		GLuint location_projection_matrix = glGetUniformLocation( programHandler, "ProjectionMatrix");
		if( location_projection_matrix >= 0 )
		{
			glUniformMatrix4fv( location_projection_matrix, 1, GL_FALSE, &projection_matrix[0][0]);
		}

		//////////////////////////////////////
		// Bind Light Settings
		glm::vec4 light_position = glm::vec4( 0.0f, 1.0f, 1.0f, 1.0f );
		glm::vec3 light_intensity = glm::vec3( 1.0f, 1.0f, 1.0f );
		glm::vec3 diffuse_reflectivity = glm::vec3( 1.0f, 1.0f, 1.0f );

		GLuint location_light_position = glGetUniformLocation( programHandler, "LightPosition");
		if( location_light_position >= 0 )
		{
			glUniform4fv( location_light_position, 1, &light_position[0]);
		}

		GLuint location_diffuse_reflectivity = glGetUniformLocation( programHandler, "Kd");
		if( location_diffuse_reflectivity >= 0 )
		{
			glUniform3fv( location_diffuse_reflectivity, 1, &diffuse_reflectivity[0]);
		}

		GLuint location_light_intensity = glGetUniformLocation( programHandler, "Ld");
		if( location_light_intensity >= 0 )
		{
			glUniform3fv( location_light_intensity, 1, &light_intensity[0]);
		}

		(*it)->update(view_matrix, model_matrix);
	}
}

void Scene::run() {
}

void Scene::addObject(GLStructure* structure) {
	objects.push_back(structure);
}

void Scene::OnKeyDown(int nKey, char cAscii) {
	if (cAscii == KEY_DIRTINESS_REDUCE) {
		dirtiness -= DIRTINESS_SPEED;
		if (dirtiness < 0) {
			dirtiness = 0;
		}
	} else if (cAscii == KEY_DIRTINESS_INCREASE) {
		dirtiness += DIRTINESS_SPEED;
		if (dirtiness > 1) {
			dirtiness = 1;
		}
	} else if (cAscii == '0') {
		setCamera(0);
	} else if (cAscii == '1') {
		setCamera(1);
	} else if (cAscii == '2') {
		setCamera(2);
	} else if (cAscii == '3') {
		setCamera(3);
	}
}

void Scene::OnMouseMove(int x, int y) {

}

void Scene::OnMouseWheel(int nWheelNumber, int nDirection, int x, int y) {

}

void Scene::setCamera(int id) {
	cameras.at(cameraSelected)->deleteListener();
	cameras.at(id)->registerListener();
	cameraSelected = id;
}
